home *** CD-ROM | disk | FTP | other *** search
/ Tech Arsenal 1 / Tech Arsenal (Arsenal Computer).ISO / tek-01 / gp.zip / GP.C < prev   
C/C++ Source or Header  |  1992-09-12  |  12KB  |  340 lines

  1. /*                A General Reentrant printf() Function
  2.  
  3.                           by Philip J. Erdelsky
  4.                           CompuServe 75746,3411
  5.                     InterNet 75746.3411@compuserve.com
  6.  
  7.                               June 8, 1992
  8.  
  9.                  PUBLIC DOMAIN -- NO RESTRICTIONS ON USE
  10.  
  11. The function printf() and its companions fprintf(), sprintf(), vprintf(), etc.,
  12. are found in every standard C library and are called by nearly every C
  13. application.  However, without source code they are rather difficult to adapt
  14. to environments other than those for which they were written, and they are not
  15. necessarily reentrant.
  16.  
  17. Our version of these functions, which we call general_printf(), is as portable
  18. and versatile as we could make it, and it is completely reentrant.  You may
  19. have to serialize access to the output device, but you won't have to serialize
  20. access to general_printf() itself.
  21.  
  22. We don't support floating-point editing, but we do support other features of
  23. the original printf() and also some add-ons.  The editing phrase %b edits an
  24. integer in binary, and the phrase %u edits an unsigned integer in decimal.
  25. Both permit the standard field parameter and the modifier l if the integer is
  26. long.  An asterisk, if used as a field or precision parameter, takes on the
  27. value of the corresponding argument, which must be an integer.  The editing
  28. phrase %x uses small letters in the edited result, the editing phrase %X uses
  29. capital letters.
  30.  
  31. We tacitly assume that function arguments of type "short" and "char" are
  32. expanded to type "int", and that the size of an argument of type "long" or
  33. "char *" is a multiple of the size of an argument of type "int".  These
  34. assumptions are true for all compilers that we are familiar with.
  35.  
  36. The call on general_printf() is as follows:
  37.  
  38.      n = general_printf(output_function, output_pointer, control_string,
  39.            argument_pointer);
  40.  
  41.      void (*output_function)(void *, int);  function to be called to output
  42.                                             a character of the edited result
  43.  
  44.      void *output_pointer;                  pointer to be passed to
  45.                                             (*output_function)()
  46.  
  47.      char *control_string;                  control string, also called a
  48.                                             format string
  49.  
  50.      int *argument_pointer;                 pointer to first argument in
  51.                                             argument list
  52.  
  53.      int n;                                 number of characters sent to
  54.                                             output device, or a negative
  55.                                             error code
  56.  
  57. The function uses the control string to edit the argument list and sends the
  58. resulting string of ASCII characters to the output device, one at a time, by
  59. calling the function (*output_function)() as follows:
  60.  
  61.      e = (*output_function)(output_pointer, c);
  62.  
  63.      void *output_pointer;   implementation-defined pointer
  64.  
  65.      int c;                  character of edited string
  66.  
  67.      int e;                  nonnegative value for successful operation,
  68.                              or negative error code
  69.  
  70. The pointer may be a file pointer, a drive number, or a pointer to some
  71. implementation-defined descriptor block.  The function general_printf() makes
  72. no assumptions about it, but merely passes it on.  The function need not
  73. return the character c if the operation is successful, but it must return a
  74. negative value if there was an error.  This value is then returned unchanged
  75. by general_printf() as its functional value.
  76.  
  77. The function general_printf() need not be called directly.  You may find it
  78. much more convenient to use it as a basis for your own versions of the standard
  79. C library functions.  Here are examples of possible alternative versions of
  80. fprintf() and sprintf() under DOS:
  81.  
  82.    #include <stdio.h>
  83.  
  84.    static int output(void *fp, int c)
  85.    {
  86.      return putc(c, (FILE *) fp);
  87.    }
  88.  
  89.    int alternative_fprintf(FILE *fp, char *control, ...)
  90.    {
  91.      return general_printf(output, fp, control, (int *)(&control+1));
  92.    }
  93.  
  94.    static int fill_string(void *p, int c)
  95.    {
  96.      *(*(char **)p)++ = c;
  97.      return 0;
  98.    }
  99.  
  100.    int alternative_sprintf(char *s, char *control, ...)
  101.    {
  102.      int n = general_printf(fill_string, &s, control, (int *)(&control+1));
  103.      *s = 0;
  104.      return n;
  105.    }
  106.  
  107. The implementation of general_printf() uses "short" arithmetic in a few places
  108. where "int" arithmetic might have been used.  Of course, if the two types are
  109. the same, there is no harm in doing this; but if "short" variables occupy two
  110. bytes and "int" variables occupy four bytes, some stack space is saved by using
  111. "short" variables.  However, there is also a much better reason.  If
  112. general_printf() is given corrupt input, it may hang up in one of its internal
  113. loops.  If the loop counter is four bytes long, the system or process may
  114. effectively freeze because executing even a tight loop over 2 billion times
  115. will take longer than the user is prepared to wait.  If the loop counter is
  116. only two bytes long, it will finish after at most 32,767 iterations.
  117.  
  118. -----------------------------------------------------------------------------*/
  119.  
  120. #define BITS_PER_BYTE           8
  121.  
  122. struct parameters
  123. {
  124.   int number_of_output_chars;
  125.   short minimum_field_width;
  126.   char options;
  127.     #define MINUS_SIGN    1
  128.     #define RIGHT_JUSTIFY 2
  129.     #define ZERO_PAD      4
  130.     #define CAPITAL_HEX   8
  131.   short edited_string_length;
  132.   short leading_zeros;
  133.   int (*output_function)(void *, int);
  134.   void *output_pointer;
  135. };
  136.  
  137. static void output_and_count(struct parameters *p, int c)
  138. {
  139.   if (p->number_of_output_chars >= 0)
  140.   {
  141.     int n = (*p->output_function)(p->output_pointer, c);
  142.     if (n>=0) p->number_of_output_chars++;
  143.     else p->number_of_output_chars = n;
  144.   }
  145. }
  146.  
  147. static void output_field(struct parameters *p, char *s)
  148. {
  149.   short justification_length =
  150.     p->minimum_field_width - p->leading_zeros - p->edited_string_length;
  151.   if (p->options & MINUS_SIGN)
  152.   {
  153.     if (p->options & ZERO_PAD)
  154.       output_and_count(p, '-');
  155.     justification_length--;
  156.   }
  157.   if (p->options & RIGHT_JUSTIFY)
  158.     while (--justification_length >= 0)
  159.       output_and_count(p, p->options & ZERO_PAD ? '0' : ' ');
  160.   if (p->options & MINUS_SIGN && !(p->options & ZERO_PAD))
  161.     output_and_count(p, '-');
  162.   while (--p->leading_zeros >= 0)
  163.     output_and_count(p, '0');
  164.   while (--p->edited_string_length >= 0)
  165.     output_and_count(p, *s++);
  166.   while (--justification_length >= 0)
  167.     output_and_count(p, ' ');
  168. }
  169.     
  170.  
  171. int general_printf(int (*output_function)(void *, int), void *output_pointer,
  172.   char *control_string, int *argument_pointer)
  173. {
  174.   struct parameters p;
  175.   char control_char;
  176.   p.number_of_output_chars = 0;
  177.   p.output_function = output_function;
  178.   p.output_pointer = output_pointer;
  179.   control_char = *control_string++;
  180.   while (control_char != '\0')
  181.   {
  182.     if (control_char == '%')
  183.     {
  184.       short precision = -1;
  185.       short long_argument = 0;
  186.       short base = 0;
  187.       control_char = *control_string++;
  188.       p.minimum_field_width = 0;
  189.       p.leading_zeros = 0;
  190.       p.options = RIGHT_JUSTIFY;
  191.       if (control_char == '-')
  192.       {
  193.         p.options = 0;
  194.         control_char = *control_string++;
  195.       }
  196.       if (control_char == '0')
  197.       {
  198.         p.options |= ZERO_PAD;
  199.         control_char = *control_string++;
  200.       }
  201.       if (control_char == '*')
  202.       {
  203.         p.minimum_field_width = *argument_pointer++;
  204.         control_char = *control_string++;
  205.       }
  206.       else
  207.       {
  208.         while ('0' <= control_char && control_char <= '9')
  209.         {
  210.           p.minimum_field_width =
  211.             p.minimum_field_width * 10 + control_char - '0';
  212.           control_char = *control_string++;
  213.         }
  214.       }
  215.       if (control_char == '.')
  216.       {
  217.         control_char = *control_string++;
  218.         if (control_char == '*')
  219.         {
  220.           precision = *argument_pointer++;
  221.           control_char = *control_string++;
  222.         }
  223.         else
  224.         {
  225.           precision = 0;
  226.           while ('0' <= control_char && control_char <= '9')
  227.           {
  228.             precision = precision * 10 + control_char - '0';
  229.             control_char = *control_string++;
  230.           }
  231.         }
  232.       }
  233.       if (control_char == 'l')
  234.       {
  235.         long_argument = 1;
  236.         control_char = *control_string++;
  237.       }
  238.       if (control_char == 'd')
  239.         base = 10;
  240.       else if (control_char == 'x')
  241.         base = 16;
  242.       else if (control_char == 'X')
  243.       {
  244.         base = 16;
  245.         p.options |= CAPITAL_HEX;
  246.       }
  247.       else if (control_char == 'u')
  248.         base = 10;
  249.       else if (control_char == 'o')
  250.         base = 8;
  251.       else if (control_char == 'b')
  252.         base = 2;
  253.       else if (control_char == 'c')
  254.       {
  255.         base = -1;
  256.         p.options &= ~ZERO_PAD;
  257.       }
  258.       else if (control_char == 's')
  259.       {
  260.         base = -2;
  261.         p.options &= ~ZERO_PAD;
  262.       }
  263.       if (base == 0)  /* invalid conversion type */
  264.       {
  265.         if (control_char != '\0')
  266.         {
  267.           output_and_count(&p, control_char);
  268.           control_char = *control_string++;
  269.         }
  270.       }
  271.       else
  272.       {
  273.         if (base == -1)  /* conversion type c */
  274.         {
  275.           char c = *argument_pointer++;
  276.           p.edited_string_length = 1;
  277.           output_field(&p, &c);
  278.         }
  279.         else if (base == -2)  /* conversion type s */
  280.         {
  281.           char *string;
  282.           p.edited_string_length = 0;
  283.           string = * (char **) argument_pointer;
  284.           argument_pointer += sizeof(char *) / sizeof(int);
  285.           while (string[p.edited_string_length] != 0)
  286.             p.edited_string_length++;
  287.           if (precision >= 0 && p.edited_string_length > precision)
  288.             p.edited_string_length = precision;
  289.           output_field(&p, string);
  290.         }
  291.         else  /* conversion type d, b, o or x */
  292.         {
  293.           unsigned long x;
  294.           char buffer[BITS_PER_BYTE * sizeof(unsigned long) + 1];
  295.           p.edited_string_length = 0;
  296.           if (long_argument) 
  297.           {
  298.             x = * (unsigned long *) argument_pointer;
  299.             argument_pointer += sizeof(unsigned long) / sizeof(int);
  300.           }
  301.           else if (control_char == 'd')
  302.             x = (long) *argument_pointer++;
  303.           else
  304.             x = (unsigned) *argument_pointer++;
  305.           if (control_char == 'd' && (long) x < 0)
  306.           {
  307.             p.options |= MINUS_SIGN;
  308.             x = - (long) x;
  309.           }
  310.           do 
  311.           {
  312.             int c;
  313.             c = x % base + '0';
  314.             if (c > '9')
  315.             {
  316.               if (p.options & CAPITAL_HEX)
  317.                 c += 'A'-'9'-1;
  318.               else
  319.                 c += 'a'-'9'-1;
  320.             }
  321.             buffer[sizeof(buffer) - 1 - p.edited_string_length++] = c;
  322.           }
  323.           while ((x/=base) != 0);
  324.           if (precision >= 0 && precision > p.edited_string_length)
  325.             p.leading_zeros = precision - p.edited_string_length;
  326.           output_field(&p, buffer + sizeof(buffer) - p.edited_string_length);
  327.         }
  328.         control_char = *control_string++;
  329.       }
  330.     }
  331.     else
  332.     {
  333.       output_and_count(&p, control_char);
  334.       control_char = *control_string++;
  335.     }
  336.   }
  337.   return p.number_of_output_chars;
  338. }
  339.  
  340.